// Copyright 2018 the V8 project authors. All rights reserved.
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.

#ifndef V8_HEAP_LOCAL_ALLOCATOR_INL_H_
#define V8_HEAP_LOCAL_ALLOCATOR_INL_H_

#include "src/heap/local-allocator.h"

#include "src/heap/spaces-inl.h"

namespace v8 {
namespace internal {

    AllocationResult LocalAllocator::Allocate(AllocationSpace space,
        int object_size,
        AllocationAlignment alignment)
    {
        switch (space) {
        case NEW_SPACE:
            return AllocateInNewSpace(object_size, alignment);
        case OLD_SPACE:
            return compaction_spaces_.Get(OLD_SPACE)->AllocateRaw(object_size,
                alignment);
        case CODE_SPACE:
            return compaction_spaces_.Get(CODE_SPACE)
                ->AllocateRaw(object_size, alignment);
        default:
            UNREACHABLE();
            break;
        }
    }

    void LocalAllocator::FreeLast(AllocationSpace space, HeapObject object,
        int object_size)
    {
        switch (space) {
        case NEW_SPACE:
            FreeLastInNewSpace(object, object_size);
            return;
        case OLD_SPACE:
            FreeLastInOldSpace(object, object_size);
            return;
        default:
            // Only new and old space supported.
            UNREACHABLE();
            break;
        }
    }

    void LocalAllocator::FreeLastInNewSpace(HeapObject object, int object_size)
    {
        if (!new_space_lab_.TryFreeLast(object, object_size)) {
            // We couldn't free the last object so we have to write a proper filler.
            heap_->CreateFillerObjectAt(object->address(), object_size,
                ClearRecordedSlots::kNo);
        }
    }

    void LocalAllocator::FreeLastInOldSpace(HeapObject object, int object_size)
    {
        if (!compaction_spaces_.Get(OLD_SPACE)->TryFreeLast(object, object_size)) {
            // We couldn't free the last object so we have to write a proper filler.
            heap_->CreateFillerObjectAt(object->address(), object_size,
                ClearRecordedSlots::kNo);
        }
    }

    AllocationResult LocalAllocator::AllocateInLAB(int object_size,
        AllocationAlignment alignment)
    {
        AllocationResult allocation;
        if (!new_space_lab_.IsValid() && !NewLocalAllocationBuffer()) {
            return AllocationResult::Retry(OLD_SPACE);
        }
        allocation = new_space_lab_.AllocateRawAligned(object_size, alignment);
        if (allocation.IsRetry()) {
            if (!NewLocalAllocationBuffer()) {
                return AllocationResult::Retry(OLD_SPACE);
            } else {
                allocation = new_space_lab_.AllocateRawAligned(object_size, alignment);
                CHECK(!allocation.IsRetry());
            }
        }
        return allocation;
    }

    bool LocalAllocator::NewLocalAllocationBuffer()
    {
        if (lab_allocation_will_fail_)
            return false;
        LocalAllocationBuffer saved_lab_ = new_space_lab_;
        AllocationResult result = new_space_->AllocateRawSynchronized(kLabSize, kWordAligned);
        new_space_lab_ = LocalAllocationBuffer::FromResult(heap_, result, kLabSize);
        if (new_space_lab_.IsValid()) {
            new_space_lab_.TryMerge(&saved_lab_);
            return true;
        }
        new_space_lab_ = saved_lab_;
        lab_allocation_will_fail_ = true;
        return false;
    }

    AllocationResult LocalAllocator::AllocateInNewSpace(
        int object_size, AllocationAlignment alignment)
    {
        if (object_size > kMaxLabObjectSize) {
            return new_space_->AllocateRawSynchronized(object_size, alignment);
        }
        return AllocateInLAB(object_size, alignment);
    }

} // namespace internal
} // namespace v8

#endif // V8_HEAP_LOCAL_ALLOCATOR_INL_H_
